package Server;

import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

import javax.xml.stream.XMLStreamException;

import org.simpleframework.xml.core.ElementException;
import org.simpleframework.xml.core.ValueRequiredException;

import GameModel.mGame;
import GameModel.mGameRound;
import GameModel.mGameStat;
import Message.MessageAlreadyReceivedException;
import Message.MessageTypes;
import Message.XMLcontent;
import Message.XMLmessage;
import Player.mPlayer;

/**
 * 
 * @author Christopher Koller
 *
 */
public class mServerSessions extends Thread {

	// ???
	static Logger log = Logger.getLogger("mServerSessions");
	// enum OpenScreens {
	// TitleScreen, GameMap, Lobby, Toplist, NewGame, PointChooser
	// }public ArrayList<OpenScreens> OpenScreens;

	// ??? heisst noch nicht überprüft auf Richtigkeit von verifizierter
	// Person ;) (CHRIS)

	boolean isTitleScreen = false;
	boolean isGameMap = false;
	boolean isLobby = false;
	boolean isToplist = false;

	static int incrementID = 0;
	static String incrementLock = "lock";
	protected int SessionID = 0;

	mPlayer player;
	Socket socket;
	mGame game;
	MessageTypes state;
	mServer controller;
	String name;

	/**
	 * Messages werden in unabhängigen Thread gestartet, sodass ein
	 * Main-ServerSession-Thread wieder nachrichten empfangen kann.Dabei soll
	 * aber trotzdem immer nur eine Nachricht aufs mal bearbeitetwerden können!
	 * By Christopher Vogel
	 */
	private ExecutorService executorService = Executors
			.newScheduledThreadPool(1);
	protected boolean sessionIsClosing = false;

	/**
	 * Konstruktor für reinen Verbindungsaufbau zwischen Client und Server ???
	 * 
	 * @author Christopher Koller
	 */
	public mServerSessions(Socket socket) {
		super();
		this.socket = socket;
		this.start();
	}

	/**
	 * Konstruktor für die Verbindung des identifierzten Client mit dem Server
	 *
	 *
	 * @author Christopher Koller
	 */
	public mServerSessions(mPlayer player, Socket socket) {
		this.player = player;
		this.socket = socket;
		this.start();
	}

	public mServerSessions(mServer controller, Socket socket) {
		this.controller = controller;
		this.socket = socket;
		this.name = socket.getInetAddress().toString(); // default ???
		this.start();
	}

	public mServerSessions(mServer controller, Socket socket, mPlayer player) {
		this.controller = controller;
		this.player = player;
		this.socket = socket;
		this.name = socket.getInetAddress().toString();
		this.start();
	}

	public mPlayer getPlayer() {
		return player;
	}

	public void setPlayer(mPlayer player) {
		this.player = player;
	}

	public mGame getGame() {
		return game;
	}

	public void setGame(mGame game) {
		this.game = game;
	}

	public Socket getSocket() {
		return socket;
	}

	public void setSocket(Socket socket) {
		this.interrupt();
		this.socket = socket;
		this.start();
	}

	/**
	 * Schliesst den Socket
	 *
	 * @author Christopher Koller
	 */

	public void close() {
		try {
			this.interrupt();
			socket.close();
		} catch (IOException e) {

		}
	}

	@Override
	public String toString() {
		return "mServerSessions [isTitleScreen=" + isTitleScreen
				+ ", isGameMap=" + isGameMap + ", isLobby=" + isLobby
				+ ", isToplist=" + isToplist + ", "
				+ (player != null ? "player=" + player + ", " : "")
				+ (socket != null ? "socket=" + socket + ", " : "")
				+ (game != null ? "game=" + game + ", " : "")
				+ (state != null ? "state=" + state + ", " : "")
				+ (controller != null ? "controller=" + controller + ", " : "")
				+ (name != null ? "name=" + name : "") + "]";
	}

	@Override
	public void run() {

		while (!this.isInterrupted()) {
			try {
				if (this.socket.isClosed()) {
					throw new SocketException();
				}
				// System.out.println(socket.isConnected());
				final XMLmessage msg = XMLmessage.receive(this.socket);
				if (this.getPlayer() != null
						&& !this.getPlayer().equals(msg.getSender())
						&& msg.getType() != MessageTypes.Pong) {
					throw new Exception(
							"this user is not logged in with this connection!");
				}
				/**
				 * Der Server kontrolliert, ob der User registriert ist und
				 * loggt ihn dann ein XMLcontent content; XMLmessage message;
				 */

				Thread th = new Thread(new Runnable() {
					public void run() {
						try {
							XMLcontent content;
							XMLmessage message;
							switch (msg.getType()) {

							case UserLogin:
								// Created by Christopher Vogel
								ArrayList<mPlayer> rPlayers = mRegisteredPlayers
										.get();
								content = new XMLcontent();
								content.setReferredMsgID(msg.getMsgID());
								mPlayer msgPlayer = msg.getContent()
										.getPlayer();
								message = new XMLmessage(null, content, msg
										.getSender());
								if (rPlayers.contains(msgPlayer)) {
									boolean alreadyLoggedIn = false;
									synchronized (controller.getClientList()) {
										for (mServerSessions ss : controller
												.getClientList()) {
											if (ss.getPlayer() != null
													&& msgPlayer != null) {
												if (ss.getPlayer().equals(
														msgPlayer)) {
													alreadyLoggedIn = true;
													break;
												}
											}
										}
									}

									if (alreadyLoggedIn) {
										message.setType(MessageTypes.ServerErrorMessage);
										content.setString("This user is already logged in!");
									} else {
										message.setType(MessageTypes.Confirmation);
										mPlayer PlayerComplete = rPlayers
												.get(rPlayers
														.indexOf(msgPlayer));
										message.getContent().setPlayer(
												PlayerComplete);
										mServerSessions.this.player = PlayerComplete;
										controller
												.userJoin(mServerSessions.this);
										content.setString("Userlogin successful");
										isLobby = true;
										isTitleScreen = false;
									}
								} else {
									message.setType(MessageTypes.ServerErrorMessage);
									content.setString("Wrong username and password combination");
								}
								message.send(mServerSessions.this.socket);

								if (isLobby) {
									try {
										Thread.sleep(25);
									} catch (InterruptedException e) {
										// TODO Auto-generated catch block
										e.printStackTrace();
									}
									controller.lobbyBroadcastPlayer();
									// controller.lobbyBroadcastGames();
									XMLmessage opengamesshow = new XMLmessage(
											MessageTypes.OpenGamesShow, null);
									opengamesshow
											.getContent()
											.setGamelist(
													controller
															.getGamesByState(mGame.eGameState.waitingForPlayers));
									opengamesshow
											.send(mServerSessions.this.socket);

									XMLmessage topplayer = new XMLmessage(
											MessageTypes.PlayerList, null);
									topplayer.getContent().setPlayerList(
											mRegisteredPlayers.get());
									topplayer.send(mServerSessions.this.socket);
								}
								break;
							case NewConnection:
								// Created by Christopher Vogel
								if (msg.getContent().getString() != null) {
									int SID = Integer.parseInt(msg.getContent()
											.getString());
									controller.MergeSession(SID,
											mServerSessions.this);
									mServerSessions.this.interrupted();
									return;

								} else {
									synchronized (incrementLock) {
										incrementID++;
										SessionID = incrementID;
									}

								}
								content = new XMLcontent();
								content.setReferredMsgID(msg.getMsgID());
								message = new XMLmessage(null, content, msg
										.getSender());
								message.setType(MessageTypes.Confirmation);
								content.setString("Connection Granted!");
								message.send(mServerSessions.this.socket);
								break;

							case UserRegistration:
								// Created by Christopher Vogel
								System.out.println("UserRegistration");
								content = new XMLcontent();
								content.setReferredMsgID(msg.getMsgID());
								message = new XMLmessage(null, content, msg
										.getSender());

								boolean PlayerAlreadyExists = false;
								synchronized (mRegisteredPlayers.get()) {
									for (mPlayer P : mRegisteredPlayers.get()) {
										if (P.getUserName().equals(
												msg.getContent().getPlayer()
														.getUserName())) {
											PlayerAlreadyExists = true;
											break;
										}
									}
								}
								if (PlayerAlreadyExists) {
									message.setType(MessageTypes.ServerErrorMessage);
									content.setString("User Registration unsuccessful - something went wrong");
								} else {
									message.setType(MessageTypes.Confirmation);
									content.setString("User Registration successful");
									mRegisteredPlayers.get().add(
											msg.getContent().getPlayer());
									mRegisteredPlayers.save();
									mServerSessions.this.player = msg
											.getContent().getPlayer();
									controller.userJoin(mServerSessions.this);
									isLobby = true;
									isTitleScreen = false;

								}
								message.send(mServerSessions.this.socket);

								if (isLobby) {
									try {
										Thread.sleep(100);
									} catch (InterruptedException e) {
										// TODO Auto-generated catch block
										e.printStackTrace();
									}
									controller.lobbyBroadcastPlayer();

									XMLmessage opengamesshow = new XMLmessage(
											MessageTypes.OpenGamesShow, null);
									opengamesshow
											.getContent()
											.setGamelist(
													controller
															.getGamesByState(mGame.eGameState.waitingForPlayers));
									opengamesshow
											.send(mServerSessions.this.socket);

									controller.lobbyBroadcastPlayer();
									XMLmessage topplayer1 = new XMLmessage(
											MessageTypes.PlayerList, null);
									topplayer1.getContent().setPlayerList(
											mRegisteredPlayers.get());
									controller.lobbyBroadcast(topplayer1);
								}

								break;
							case NewGame:
								// Created by Christopher Vogel
								mGame newGame = msg.getContent().getGame();
								game = new mGame(newGame.getGameName(), newGame
										.getPlayerNumber(), newGame
										.getMaxPoints(), msg.getSender());
								controller.addGame(game);
								// Player wird schon im konstruktor mitgegeben!
								System.out.println(game);
								System.out.println(controller.getOpenGames());
								controller.lobbyBroadcastGames();
								break;
							case JoinGame:
								// Created by Christopher Vogel
								mGame msgGame = msg.getContent().getGame();
								mGame listGame = null;
								if (controller.getClientList() != null) {
									for (mGame g : controller.getOpenGames()) {
										if (g.equals(msgGame)) {
											listGame = g;
										}
									}
									if (listGame == null) {
										// Fehlermeldung:
										XMLmessage msgError = new XMLmessage(
												MessageTypes.ServerErrorMessage,
												null);
										msgError.getContent().setReferredMsgID(
												msg.getMsgID());
										msgError.getContent()
												.setString(
														"This game does not exist anymore!");
										msgError.send(socket);
									}
									if (game == null && listGame != null) {
										synchronized (listGame) {
											// mGame GameWithoutJoin =
											// listGame.clone();
											if (!listGame
													.isPossible2addPlayerKonto(msg
															.getSender())) {
												XMLmessage msgError = new XMLmessage(
														MessageTypes.ServerErrorMessage,
														null);
												msgError.getContent()
														.setReferredMsgID(
																msg.getMsgID());
												msgError.getContent()
														.setString(
																"The game already has enough players. Try another game!");
												msgError.send(socket);
											} else {

												XMLmessage msgConf = new XMLmessage(
														MessageTypes.Confirmation,
														null);
												msgConf.getContent()
														.setReferredMsgID(
																msg.getMsgID());
												msgConf.getContent().setGame(
														listGame);
												msgConf.send(socket);

												// System.out.println("pong received for join!"+msgConf.waitingForPong(socket,
												// msgConf));
												while (!msgConf
														.isPongReceived(socket)) {
													try {
														Thread.sleep(50);
													} catch (InterruptedException e) {
														// TODO
														// Auto-generated
														// catch block
														e.printStackTrace();
													} /*
													 * waiting...
													 */
													System.out
															.println("waiting");
												}
												System.out
														.println("end waiting...");
												listGame.addPlayerKonto(msg
														.getSender());

												// Am Ende muss allen
												// spielern
												// mitgeteilt
												// werden, dass ein Spieler
												// hinzugef�gt
												// wurde!
												controller.gameBroadcast(msg,
														listGame);
												game = listGame;
												if (listGame.isGameRunning()) {
													controller
															.gameBroadCastNewRound(listGame);
												}
											}
										}
									}
								}
								controller.lobbyBroadcastGames();
								//
								break;
							case NewCardCombo:
								// Created by Christopher Vogel
								System.out.println("Case NewCardCombo");
								if (!game.isGameFinished()) {
									if (game.newCardCombo(msg.getContent()
											.getCards(), msg.getSender())) {
										// f�r die richtige Sortierung
										try {
											msg.getContent().setCards(
													game.getLastCardCombo()
															.getCardList());
										} catch (Exception e) {
											// Eine Neue Runde wird schon vorher
											// erzeugt,
											// deshalb ben�tigt es dies
											mGameRound tmpRound = game
													.getGameRoundList().pop();
											msg.getContent().setCards(
													game.getLastCardCombo()
															.getCardList()); //
											game.getGameRoundList().add(
													tmpRound);
										}
										controller.gameBroadcast(msg, game);
										System.out
												.println(mServerSessions.this.game
														.getLastGameRound()
														.getRoundState());

										if (game.isGameFinished()) {
											try {
												System.out
														.println("Game has finished!");
												mRegisteredPlayers
														.calculateStatistics(game);

												XMLmessage topplayer0 = new XMLmessage(
														MessageTypes.PlayerList,
														null);
												topplayer0.getContent()
														.setPlayerList(
																mRegisteredPlayers
																		.get());
												controller
														.lobbyBroadcast(topplayer0);
											} catch (Exception e) {
												e.printStackTrace();
											}
										} else if (mServerSessions.this.game
												.getLastGameRound()
												.getRoundState() == mGameRound.eRoundState.RoundIsWaiting) {
											controller
													.gameBroadCastNewRound(game);
											mServerSessions.this.game
													.getLastGameRound();

										}
										System.out.println("gamestatus"
												+ game.getGameState());
										for (mGameStat gs : game
												.getPlayerIngameKonto()) {
											System.out.println(gs.getPlayer()
													+ " " + gs.getGamePoints());
										}
									} else {
										System.out
												.println("NewCardCombo geht nicht");

									}
								}
								// if (listGame.isGameRunning()) {
								// controller.gameBroadCastHandCards(listGame);
								// }
								// mCardList.checkCardList(,msg.getSender());

								break;
							case Pass:
								// Created by Christopher Vogel
								if (mServerSessions.this.game.addPassPlayer(msg
										.getSender())) { // Fehler
									controller.gameBroadcast(msg,
											mServerSessions.this.game);
								} else {
									System.out.println("Wrong pass turn!");
								}
								break;
							case Bet:
								// Created by Christopher Vogel
								game.addBet(msg.getContent().getString(),
										msg.getSender());
								controller.gameBroadcast(msg,
										mServerSessions.this.game);
								break;
							case ChatMessage:
								// Created by Thomas Felder
								System.out.println("Case Controller");
								controller.gameBroadcast(msg, game);
								System.out
										.println("gameBroadcast durchgef�hrt");
								System.out.println(controller.getClientList());
								System.out.println(game);
								//
								break;
							case UserExitWithoutLogout:
								// Created by Christopher Vogel
								mServerSessions.this.closeGame();
								break;
							case ChooseBombPointReceiver:
								// Created by Christopher Vogel
								if (mServerSessions.this
										.getGame()
										.getLastTrickPlay()
										.bombsWithTree(
												msg.getContent().getPlayer(),
												game)) {
									controller.gameBroadcast(msg, game);
								}

								break;
							default:
								break;
							}
						} catch (Exception e) {
							mServerSessions.this.ExceptionHandling(e);
						}
					}
				});
				executorService.execute(th);

			} catch (Exception e) {
				this.ExceptionHandling(e);
			}
			/*
			 * catch (ValueRequiredException | XMLStreamException e) { }
			 * 
			 * if (e.getMessage().equals(
			 * "java.net.SocketException: Connection reset")) { closeSession(e);
			 * } else { e.printStackTrace(); } // if (socket.isClosed()) {
			 * break; }
			 * 
			 * } catch (SocketException e) { closeSession(e); } catch
			 * (MessageAlreadyReceived e) {
			 * log.info("Message already received!"); } catch (ElementException
			 * e) { log.info("Message ist not XML!"); // e.printStackTrace(); }
			 * catch (Exception e) { e.printStackTrace();
			 * System.out.println(e.getMessage()); }
			 */
		}
	}

	/**
	 * Dieses Exceptionhandling kommt an mehreren Stellen zum Zuge (bspw.
	 * run()-receive() und run()-receiveverarbeitung...)
	 * 
	 * @author Christopher Vogel
	 * @param ex
	 */
	public void ExceptionHandling(Exception ex) {
		try {
			throw ex;
		} catch (ValueRequiredException | XMLStreamException e) {

			if (e.getMessage().equals(
					"java.net.SocketException: Connection reset")
					|| e.getMessage()
							.equals("java.net.SocketException: Software caused connection abort: recv failed")) {
				closeSession(e);
			} else {
				e.printStackTrace();
			}
			// if (socket.isClosed()) { break; }

		} catch (SocketException | InterruptedException e) {
			closeSession(e);
		} catch (MessageAlreadyReceivedException e) {
			log.info("Message already received!");
		} catch (ElementException e) {
			log.info("Message ist not XML!");
			// e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(e.getMessage());
		}
	}

	/**
	 * Beende eine Session eines Users vollständing und beende sein laufendes
	 * Spiel!
	 * 
	 * @author Christopher Vogel
	 * @param e
	 */
	public void closeSession(Exception e) {

		this.sessionIsClosing = true;

		try {
			log.info("Socket connection lost - try to reconnect!"/*
																 * Because of:"
																 * +
																 * e.getMessage
																 * ()
																 */);
			Thread.sleep(5000);
		} catch (InterruptedException e2) {
		} finally {
			if (!this.sessionIsClosing) {
				log.info("Reconnection successful!");
				return;
			}
		}
		synchronized (this) {
			if (!this.isInterrupted()) {
				try {
					log.info("Socket has closed!"/* Because of:" + e.getMessage() */);
					// e.printStackTrace();
					socket.close();
				} catch (Exception e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();

				}

				controller.removeClient(this);
				this.closeGame();
				this.close();
			}
		}
	}

	/**
	 * Schliesse das Spiel vollständig!
	 * 
	 * @author Christopher Vogel
	 */
	private void closeGame() {
		if (game != null) {
			System.out.println("game is deleted!");
			XMLmessage msg = new XMLmessage(MessageTypes.UserExitWithoutLogout,
					player);
			try {
				game.removePlayerKonto(this.player);
			} catch (Exception e00) {

			}
			controller.gameBroadcast(msg, game);
			game.getPlayerIngameKonto().clear();
			controller.removeGame(game);
			controller.lobbyBroadcastPlayer();
			controller.lobbyBroadcastGames();
			game = null;
		}
	}
}
